type State = 'pending' | 'fullfilled' | 'rejected'
/**
 * state = 'fullfilled' 执行resolve
 * state = 'rejected' 执行reject
 * 
 * state = 'fullfilled'
    * 如果callback 不是函数 --> resolve(this.value)
    * const x = callback(this.value)
    * 如果 x 不是promise对象 --> resolve(x) or reject(x) 和当前状态保持一致
    * 如果 x 是promise对象 --> x.then(resolve, reject)
 */
type Handler = {
  state: State,
  callback: Function,  // .then(callback) then的回调函数
  resolve: Function,  // nextPromise, then返回的promise对象的resolve方法
  reject: Function  // nextPromise, then返回的promise对象的reject方法
}

export default class MyPromise {
  private state: State = 'pending'
  private value: any = undefined
  private handlers: Handler[] = []

  constructor(executor) {
    const { resolve, reject } = this
    try {
      executor(resolve.bind(this), reject.bind(this))
    } catch (e) {
      this.reject(e)
      console.error(e)
    }
  }

  private resolve(value) {
    this.setState('fullfilled', value)
  }

  private reject(reason) {
    this.setState('rejected', reason)
  }

  private setState(state: State, value: any) {
    if (this.state !== 'pending') return // 防止连续resove或reject
    /**
     * 状态吸收
     * const p1 = Promise.reject(1)
     * const p2 = new Promise(reso => reso(p1))
     */
    if (isPromise(value)) {
      value.then(this.resolve.bind(this), this.reject.bind(this))
      return
    }
    this.state = state
    this.value = value
    this.runHandlers()
  }

  private runHandlers() {
    if (this.state === 'pending') return // 专为 then 方法准备的
    while (this.handlers.length) {
      const handler = this.handlers.shift()
      this.runHandler(handler)
    }
  }

  private runHandler({ state, callback, resolve, reject }: Handler) {
    if (this.state !== state) {
      return
    }
    microTask(() => {
      if (typeof callback !== 'function') {
        this.state === 'fullfilled'
          ? resolve(this.value)
          : reject(this.value)
        return
      }

      try {
        const x = callback(this.value)
        isPromise(x) === true
          ? x.then(resolve, reject)
          : resolve(x)
      } catch (e) {
        reject(e)
        console.error(e)
      }
    })
  }

  private pushHandler(state: State, callback: Function, resolve: Function, reject: Function) {
    this.handlers.push({
      state,
      callback,
      resolve,
      reject
    })
  }

  then(onFulfilled?, onRejected?) {
    return new MyPromise((resolve, reject) => {
      this.pushHandler('fullfilled', onFulfilled, resolve, reject)
      this.pushHandler('rejected', onRejected, resolve, reject)
      this.runHandlers() // Promise.resolve.then()
    })
  }

  catch(onRejected) {
    return this.then(null, onRejected)
  }

  finally(callback: Function) {
    return this.then(
      res => {
        callback()
        return res
      },
      err => {
        callback()
        throw err
      }
    )
  }

  static resolve(value) {
    if (isPromise(value)) {
      return value
    }
    return new MyPromise(resolve => resolve(value))
  }

  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason))
  }

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const values = []
      let count = 0

      const add = (i, v) => {
        values[i] = v
        count++
        if (count === promises.length) resolve(values)
      }

      promises.forEach((p, i) => {
        MyPromise.resolve(p).then(
          res => add(i, res),
          rej => reject(rej)
        )
        // 其实可以写成下面这样, 但p就必须是promise对象,不然会出错
        // p.then(
        //   res => add(i, res),
        //   rej => reject(rej)
        // )
      })
    })
  }

  static race(promises: MyPromise[]) {
    return new MyPromise((resolve, reject) => {
      promises.forEach(p => {
        p.then(resolve, reject)
      })
    })
  }

}
function microTask(callback: Function) {
  if (globalThis.process && globalThis.process.nextTick) {
    globalThis.process.nextTick(callback)
  } else {
    setTimeout(callback, 0)
  }
}

function isFn(v) {
  return typeof v  === 'function'
}

function isObj(v) {
  return typeof v === 'object' && v !== null
}

function isPromise(p) {
  if (
    (isObj(p) || isFn(p)) && p.then && isFn(p.then)
  ) {
    return true
  }
  return false
}